package org.example;

import java.time.Duration;

public class Profiler implements Runnable{

    private volatile boolean stop = false;

    private final Options options;

    private final Store store;

    private Thread thread;

    public Profiler(Options options) {
        this.options = options;
        this.store = new Store(options.getFlamePath());
        Runtime.getRuntime().addShutdownHook(new Thread(this::stop));
        this.thread = Thread.currentThread();
    }

    @Override
    public void run() {
        while (!stop) {
            Duration start = Duration.ofNanos(System.nanoTime());
            sample();
            Duration duration = Duration.ofNanos(System.nanoTime()).minus(start);
            Duration sleepDuration = options.getInterval().minus(duration);
            try {
                sleep(sleepDuration);
            } catch (InterruptedException e) {
                break;
            }
            if (options.isPrintMethodTable()) {
                store.printMethodTable();
            }
            store.storeFlameGraphIfNeeded();
            stop = false;
        }
    }

    private void sample() {
        // sampling profiler data
        // Thread::getAllStackTraces will just cause one safe point bias
        // while each Thread.currentThread().getStackTrace() will cause one
        Thread.getAllStackTraces().forEach((thread, stackTraceElements) -> {
            if (!thread.isDaemon()) {
                store.addSample(stackTraceElements);
            }
        });

    }

    private void sleep(Duration duration) throws InterruptedException {
        if (duration.isNegative() || duration.isZero()) {
            return;
        }
        Thread.sleep(duration.toMillis(), duration.toNanosPart() % 1000000);
    }

    private void stop() {
        stop = true;
        while (stop);
    }

}
